/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.simba.similarity;

import org.simba.utils.Configuration;
import uk.ac.shef.wit.simmetrics.similaritymetrics.QGramsDistance;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import java.util.HashMap;
import java.util.TreeSet;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.simba.utils.EntityUriMap;
import org.simba.utils.NamedEntity;
import org.simba.utils.PorterStemmer;
import org.simba.utils.StopWords;

/**
 * TESTED
 * Implements a similarity function based on QGrams. If resource are found that
 * match the input perfectly, then no further resources are retrieved. Else, similar
 * entities (according to bif:contains) are retrieved.
 * @author ngonga
 */
public class TrigramsSimilarity implements SimilarityFunction {

    static Logger logger = Logger.getLogger("AEON");

    @Override
    public HashMap<String, TreeSet<String>> getUriLabels(String endpoint,
            String graph, TreeSet<String> labelProperties) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public EntityUriMap getMappings(String inputText, TreeSet<NamedEntity> entities,
            HashMap<String, TreeSet<String>> uriLabels) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public EntityUriMap getMappings(String inputText, TreeSet<NamedEntity> entities,
            String sparqlEndpoint, String graph, TreeSet<String> labelProperties) {
        QGramsDistance q = new QGramsDistance();
        EntityUriMap map = new EntityUriMap();
        for (NamedEntity e : entities) {
            HashMap<String, Double> uris = defaultGet(e, sparqlEndpoint, graph,
                    labelProperties);
            if (uris.isEmpty()) {
                //logger.info("No perfect matches. Trying partial matches.");
                //uris = complexGet(e, sparqlEndpoint, graph, labelProperties);
            }
            uris = transformUris(uris);
            map.put(e, uris);
        }
        //result
        double sim;
        EntityUriMap result = new EntityUriMap();
        TreeSet<String> inputKeys = getStringSet(inputText);
        for (NamedEntity e : map.keySet()) {
            HashMap<String, Double> uris = new HashMap<String, Double>();
            for (String s : map.get(e).keySet()) {
                sim = q.getSimilarity(mergeAllLabels(s, sparqlEndpoint, 
                        graph, labelProperties).toString(), inputKeys.toString());
                uris.put(s, sim);
                logger.info(e + " "+mergeAllLabels(s, sparqlEndpoint,graph, labelProperties).toString()+ 
                        " "+inputKeys.toString());
            }
            result.put(e, uris);
        }
        return result;
    }

    /** Transform YAGO Class URIs into DBpedia URIs
     * 
     * @param uris List of uris to transform
     * @return Transformed uris
     */
    public static HashMap<String, Double> transformUris(HashMap<String, Double> uris) {
        //logger.info("Transformation input = " + uris);
        HashMap<String, Double> result = new HashMap<String, Double>();
        TreeSet<String> knownUris = new TreeSet<String>();
        if (uris == null) {
            return null;
        }
        if (uris.isEmpty()) {
            return uris;
        }
        String transformedUri;
        for (String uri : uris.keySet()) {
            transformedUri = transformUri(uri).trim();

            if (!knownUris.contains(transformedUri)) {
                if (!transformedUri.contains("Category:") && !transformedUri.contains("%")
                        &&!transformedUri.contains("/property/")) {
                    result.put(transformedUri, uris.get(uri));
                }
                knownUris.add(transformedUri);
            }
        }

        //logger.info("Transformation output = " + result);
        return result;
    }

    /** Transform YAGO Class URIs into DBpedia URIs
     * 
     * @param s Uri to transform
     * @return Transformed uri
     */
    public static String transformUri(String s) {
        if (s.contains("http://dbpedia.org/class/yago/")) {
            s = s.replaceAll("http://dbpedia.org/class/yago/", "");
            s = s.replaceAll("(.)([A-Z])", "$1_$2");
            s = s.replaceAll("%28", ",");
            s = s.replaceAll("%29", "");
            s = "http://dbpedia.org/resource/" + s;
        }
        return s;
    }

    /**
     * Check for perfectly matching URIs
     * @param label Label of entity
     * @param endpoint SPARQL endpoint
     * @param graph Graph to use (set to null if not required)
     * @return List of mapping URIs. Score is 1 (perfect match)
     */
    public HashMap<String, Double> defaultGet(NamedEntity e, String endpoint,
            String graph, TreeSet<String> labelProperties) {

        HashMap<String, Double> result = new HashMap<String, Double>();
        String query = "SELECT ?x WHERE {";
//        if (e.type != null) {
//            if (e.type.toLowerCase().startsWith("loc")) {
//                query = query + "?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://dbpedia.org/ontology/Place>. ";
//            } else if (e.type.toLowerCase().startsWith("per")) {
//                query = query + "?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://dbpedia.org/ontology/Person>. ";
//            } else if (e.type.toLowerCase().startsWith("org")) {
//                query = query + "?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://dbpedia.org/ontology/Organization>. ";
//            }
//        }
        if (labelProperties.size() == 0) {
            //assume rdfs:label
            query = query + "?x <http://www.w3.org/2000/01/rdf-schema#label> \"" + e.label + "\"@en. ";
        } else {
            for (String prop : labelProperties) {
                query = query + "{?x " + prop + " \"" + e.label + "\"@en} UNION";
            }
            query = query.substring(0, query.lastIndexOf("UNION"));
        }
        query = query + "}";
        System.out.println(query);
        Query sparqlQuery = QueryFactory.create(query);
        QueryExecution qexec;
        if (graph != null) {
            qexec = QueryExecutionFactory.sparqlService(endpoint, sparqlQuery, graph);
        } else {
            qexec = QueryExecutionFactory.sparqlService(endpoint, sparqlQuery);
        }
        ResultSet results = qexec.execSelect();
        String resource;
        try {
            while (results.hasNext()) {
                QuerySolution soln = results.nextSolution();
                resource = soln.getResource("?x").toString();
                result.put(resource, 1.0);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            logger.warn(ex.getMessage());
        }
        return result;
    }

    /**
     * Check for URIs that contain entry as substring
     * @param label Label of entity
     * @param endpoint SPARQL endpoint
     * @param graph Graph to use (set to null if not required)
     * @return List of mapping URIs. Score is 1 (perfect match)
     */
    public HashMap<String, Double> complexGet(NamedEntity e, String endpoint,
            String graph, TreeSet<String> labelProperties) {

        HashMap<String, Double> result = new HashMap<String, Double>();
        String query = "SELECT ?x ?l WHERE {";
        if (labelProperties.size() == 0) {
            //assume rdfs:label
            query = query + "?x <http://www.w3.org/2000/01/rdf-schema#label> ?l. ?l <bif:contains> \"" + e.label + "\"";
        } else {
            for (String prop : labelProperties) {
                query = query + "{?x " + prop + "?l. ?l <bif:contains> \"" + e.label + "\"} UNION";
            }
            query = query.substring(0, query.lastIndexOf("UNION"));
        }
        query = query + "}";
        System.out.println(query);
        Query sparqlQuery = QueryFactory.create(query);
        QueryExecution qexec;
        if (graph != null) {
            qexec = QueryExecutionFactory.sparqlService(endpoint, sparqlQuery, graph);
        } else {
            qexec = QueryExecutionFactory.sparqlService(endpoint, sparqlQuery);
        }
        ResultSet results = qexec.execSelect();
        String label, uri;
        QGramsDistance qgrams = new QGramsDistance();
        try {
            while (results.hasNext()) {
                QuerySolution soln = results.nextSolution();
                label = soln.getLiteral("?l").toString();
                uri = soln.getResource("?x").toString();
                result.put(uri, new Double(qgrams.getSimilarity(label.toLowerCase(), e.label.toLowerCase())));
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            logger.warn(ex.getMessage());
        }
        return result;
    }

    /** Merges all labels of a given URI
     * 
     * @param uri URI whose labels are to be merged
     * @param endpoint SPARQL endpoint
     * @param graph Graph to use (null if not needed)
     * @param labelProperties Properties used as label (default = rdfs:label)
     * @return All strings contained in the labels of the URI
     */
    public TreeSet<String> mergeAllLabels(String uri, String endpoint, String graph,
            TreeSet<String> labelProperties) {
        TreeSet<String> strings = new TreeSet<String>();
        String query = "SELECT ?l WHERE {";
        if (labelProperties.isEmpty()) {
            //assume rdfs:label
            query = query + "<" + uri + "> <http://www.w3.org/2000/01/rdf-schema#label> ?l. ";
        } else {
            for (String prop : labelProperties) {
                query = query + "{ <" + uri + "> " + prop + "?l. } UNION";
            }
            query = query.substring(0, query.lastIndexOf("UNION"));
        }
        query = query + "}";
        //logger.info(query);
        Query sparqlQuery = QueryFactory.create(query);
        QueryExecution qexec;
        if (graph != null) {
            qexec = QueryExecutionFactory.sparqlService(endpoint, sparqlQuery, graph);
        } else {
            qexec = QueryExecutionFactory.sparqlService(endpoint, sparqlQuery);
        }
        ResultSet results = qexec.execSelect();
        TreeSet<String> labels;
        String label;
        QGramsDistance qgrams = new QGramsDistance();
        try {
            while (results.hasNext()) {
                QuerySolution soln = results.nextSolution();
                label = soln.getLiteral("?l").toString();
                String split[] = label.split(" ");
                for (int i = 0; i < split.length; i++) {
                    //label = process(split[i]);
                    if (split[i].length() > 0) {
                        strings.add(split[i]);
                    }
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            logger.warn(ex.getMessage());
        }

        return strings;
    }

    public TreeSet<String> getStringSet(String s) {
        TreeSet<String> result = new TreeSet<String>();
        String split[] = s.split(" ");
        String token;
        for (int i = 0; i < split.length; i++) {
            token = process(split[i]);
            if (token.length() > 0) {
                result.add(token);
            }
        }
        return result;
    }

    /** Computes the Jaccard similarity of two sets of strings
     * 
     * @param a First set of strings
     * @param b Second set of strings
     * @return Similarity value
     */
    public double jaccard(TreeSet<String> a, TreeSet<String> b) {

        if (a == null || b == null) {
            logger.fatal("Empty labels");
            return 0;
        }
        if (a.isEmpty() && b.isEmpty()) {
            return 0;
        }
        double intersection = 0.0;
        for (String s : a) {
            if (b.contains(s)) {
                intersection++;
            }
        }
        logger.info("Jaccard(" + a + ", " + b + ") = " + intersection / (double) (a.size() + b.size() - intersection));
        return intersection / (double) (a.size() + b.size() - intersection);
    }

    public String process(String s) {
        TreeSet<String> result = new TreeSet<String>();
        StopWords sp = new StopWords();
        s = s.toLowerCase();
        s = s.replaceAll(Pattern.quote("("), "");
        s = s.replaceAll(Pattern.quote(")"), "");
        s = s.replaceAll(Pattern.quote("."), "");
        s = s.replaceAll(Pattern.quote("'s"), "");
        if (s.contains("@")) {
            s = s.substring(0, s.lastIndexOf("@"));
        }
        if (sp.isStopWord(s)) {
            return "";
        } else {
            return s;
            //return (new PorterStemmer()).stemWord(s);
        }
    }

    public static void main(String args[]) {
        System.out.println(transformUri("http://dbpedia.org/class/yago/CentralProvince%28Kenya%29"));
//        System.out.println((new TrigramsSimilarity()).mergeAllLabels("http://dbpedia.org/resource/Limbe,_Malawi",
//                "http://live.dbpedia.org/sparql", null, new TreeSet<String>()));
//        System.out.println((new TrigramsSimilarity()).getStringSet("There is also a Limbe located in Malawi."));
    }

    @Override
    public EntityUriMap getMappings(Configuration c, TreeSet<NamedEntity> entities) {
        return getMappings(c.inputText, entities, c.sparqlEndpoint, c.graph, c.labelProperties);
    }
}
